-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generic fft interface #8
Conversation
# Conflicts: # test/fft_compile.cpp
Hi @Lagrang3 this is a nice development and this PR is a good step forward. I am slightly concerned with this line and its follow-up lines. Basically, there is later in the file a default template parameter relying on the BSL FFT backend. This is syntactically OK. But if the situation were changed from the point of FFTW, then there would be a BSL template file having a dependence on FFTW. Now having a dependence on BSL header-only DFT-backend is OK. But I wonder if this hints at a slight problem in class/template granularity somewhere such that the main FFT header file is, in fact, dependent on at least one of the backends? A forward template class declaration is not enough because the BSL dft is actually subsequently used as a base class in further class declarations, so all of the FFT's require the BSL backend. I don't know, off the top of my head how to resolve this or if it is even worth resolving? Let's consider this. |
I found, prior to creating this PR, use of |
Hi @ckormanyos , could you elaborate on this? We could remove the depedency of the main header from the BSL backend by just removing the line and removing the default backend. The FFTW backend does not depend on the BSL backend. |
Of course. You are right. I had tried to remove the include line from the header. But then, I had absentmindedly forgotten to include the backend-type header in my test code. So let's please do this in my opinion. The main header should not contain the backend headers. If this means that the client always has to specify the type of backend in the template instantiation, then i can live with that. @Lagrang3 : Thoughts? |
In my commit "Try CI w/out BSL default but design still not right", it's still not quite right. I want it to be analogous to Boost.Multiprecision. In that one, inclusion of the backend type automatically includes the boilerplace |
OK. This got green again. I do, however, believe that the symbiosis between the backend/bolierplate headers can be improved. This exercise revealed a bunch of probs with MSVC on the prototype code (mostly gone now). So this motivates me to get cracking on #7 (to be started soon...). |
I'll see what I can do. |
Two program artifacts come to mind, either a namespace localization or a static-polymorphic template struct or class which serves as nothing but a holder for the FFT functions and the type of the underlying FFT object. Consider the latter, a static-polymorphic template struct or class. If we were to define a template struct or class class, as follows:
We could replace the line:
With the call to the static function I'm not sure if this is good or already redundant with some of the stuff already going on in our templates. But I would (and will) start thinking along these lines. |
typename T::value_type, | ||
decltype(sin(std::declval<typename T::value_type>())), | ||
decltype(cos(std::declval<typename T::value_type>())) | ||
> > : std::true_type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a very nice template. However I am a little bit concerned with it's name. Maybe better to be more explicit that it checks for presence of sin(…)
and cos(…)
. Like maybe has_sin_cos<…>
?
Following this, I think we should to go through Boost libs and add tests for stuff which happens to have sin(…)
and cos(…)
, this one is the first contender.
It's also possible to calculate the sin(…)
and cos(…)
of a matrix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So far we have two kinds of template algorithms:
- those that assume the type is complex so that we can use sin/cos to compute the roots of unity (so that we don't have to ask for the user for the roots and we don't have to use power functions to derive smaller roots from larger ones);
- and those who would work even in a larger domain (this domain include complex as well) of algebraic ring types.
The intention of this template is to tell if the argument represents a complex number, so that we can choose one DFT implementation or the other. Checking for sin/cos is a first approach to deduce the answer for unknown types. We could change the logic if needed to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I see. Then maybe simpler it would be to use the native boost::is_complex
from #include <boost/type_traits/is_complex.hpp>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would be great! But the documentation says:
"If T is a complex number type then true (of type std::complex for some type U), otherwise false."
That means (I've just tested)
boost::is_complex<boost::multiprecision::cpp_complex_quad>::value
is false
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, we could alias and specialize the template boost::is_complex
inside the boost::math::fft
namespace to evaluate to true
all boost::multiprecision::cpp_complex<...>
types. But then I guess boost::math
would be dependent on boost::type_traits
and boost::multiprecision
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That means (I've just tested)
boost::is_complex<boost::multiprecision::cpp_complex_quad>::value
is false.
@ckormanyos , this seems like a bug to me, what would you say about this?
EDIT: or maybe there is some other is_complex
template for checking multiprecision stuff, like multiprecision::is_complex
?
Then boost::is_complex<…> or boost::multiprecision::is_complex<…>
would do the trick :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for the Type Traits library, that's not a bug
https://github.com/boostorg/type_traits/blob/develop/include/boost/type_traits/is_complex.hpp
The value is true if the type is std::complex<...>
otherwise false. Hence the template tells if the type is "complex", where "complex" means std::complex
.
Multiprecision has its own definition of the is_complex
template
https://github.com/boostorg/multiprecision/blob/develop/include/boost/multiprecision/traits/is_complex.hpp
But it does the same thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for checking this. We will have time figure this out. Maybe for example check if the type uses complex_adaptor<…>
. But it's not urgent. What matters is that now I understand your intentions in this part of code :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In complex_adaptor
, we use things like
enable or test a Boolean value from:
boost::multiprecision::detail::is_complex<Result>::value
I need to check again if this works for non-built-in (i.e., multiprecision) types.
But here is a rather restricting constraint. Very much work was done to make Boost.Math standalone, requiring no other Boost library dependencies other than itself. It might be incongruent with the evolution of Boost.Math to have FFT depend on Boost.Multiprecision.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.. to have FFT depend on Boost.Multiprecision.
I definitely agree with that. We will need to do more SFINAE (or #ifdef :) ) tricks to make sure that a simple line that calls FFT on a simple double type does not pull extra dependencies with it.
void_t< | ||
typename T::value_type, | ||
decltype(sin(std::declval<typename T::value_type>())), | ||
decltype(cos(std::declval<typename T::value_type>())) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm.. if I get the intentions correctly it should be checking for presence of
decltype(sin(std::declval<T>())),
decltype(cos(std::declval<T>()))
Or did I misunderstood something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, you are computing sin(…)
and cos(…)
of phase ∈ ℝ. OK. I will need to ponder this for a while.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it is not so important to compute the sin/cos of the complex number. Our implementation needs the sin/cos of the underlying real phase.
You mean we could provide the following interface, for example
to alias our current
|
I believe there are two fundamental methods, both potentially using aliases. One method would amount to putting the template specializations of functions like A second method would be to place public static wrapper functions such as |
OK. Then I understood correctly. |
I've pushed the convolution implementation and Rader's algorithm for prime number sizes. Rader's implementation needs convolution routines to be implemented in O(n log n) for it to have O(n log n) complexity as well. The convolution that I uploaded will rescale the input into a power of two size array in order to profit from the most efficient of our FFTs. Before this commit, the composite size FFT had complexity O(n (p1 + p2 + p3 + ...)) complexity where the pi's are the prime factors of n. That means that for example n = p1 p2, the multiplication of two big prime numbers, we are very inneficient. The code is a bit messy and I have used naive discrete mathematics, but at least it works with the tests. |
This is moving along well. Good job @Lagrang3 Somewhere in your commit, I saw something that might look like the pwoer-modulus function. I often prefer to use the so-called ladder method for pow-mod function. i can contribute one if you like? |
Show us the ladder method. I never heard of it. Thank you. By the way, I was thinking of writing a custom type for modular arithmetics and then re-use template power function. |
Great, thanks! I'll have a look. |
Nice work! @Lagrang3 and @cosurgi it seems like at least some of the generic FFT interface work is actually done. A clerical issue...? Does it make sense to merge this to the main branch of the fork and either close this issue and raise new issues? To me it yould seem nice to get all of the prime sizes work, etc. onto the main branch of the fork so that new branches automatically get it. Ideas on this clerical issue? |
... Or merge the PR, but keep the branch open if necessary for potential later PR? Or just keep the PR open? |
@ckormanyos Keep this one open. The generic vs complex interface still needs refinement. |
Understood. Thanks for clarification @Lagrang3. So when I get a little time slot to add some more compilers/OS-es in CI, then I'll do it preferably here where the action is going on then... |
If we now work on this branch instead, then perhaps it makes sense to put here some commits from other branch, to avoid merge conflicts in the future. For example this commit seems to be omitted: 2162cac, there could be more though. |
Sure. Feel free to merge |
There is kind of a flow for this kind of thing. A branch with a very long, lively length of existence is inherently a bit dangerous. Usually we merge branches periodically to the main branch. Others on branches subsequently merge develop into their local branches prior to making PRs (PRs which are then dealt with in a timely fashion). And the whole development flow moves along in a coherent, coordinated fashion. It might actually make sense to figure out what should be merged to main, what else and then actually coherently merge these things. Basically, I feel uneasy when the integrity and tracked nature of the develop branch is lost in favor of feature branches. We might want to re-establish develop in the next week or so... |
I have little experience on this matters. But to my opinion, having multiple branches has the scope of putting many people working coherently in the same project. As a single person, I can hardly work on two features at the same time. Therefore, I prefer having my own master branch, I agree that once this PR has fulfilled its purpose, it can be merged into Right now I am in the mood to look into the allocator stuff. Hence I might use the last commit here as starting point for another branch to work on those. |
Good job on all this so far. It's really moving forward.
That above makes sense although I must admit, I'm not a huge fan of branching from branches. But you know different people have different ways of working. So please find your own preference. We have just a small number of developers in this project right now so we can keep most stuff straight however we decide to move forward. I think it might make sense reather sooner than later to call one of these branches or a combination of a few of them to be, let's say, not complete, but representative of the best state of the work so far. At such times in the project progress, merges to develop really could make sense. The "generic fft interface" branch can stay active and another PR can be raised immediately, but it might make sense to get some of the really good results you already have into develop on the fork. In this way, develop of our fork could sort of play the role of the best state of this project, passing CI, etc. and keep moving forward. Basically, I'd recommend every week or so, certainly after 2 or 3 weeks, to push develop forward. But stick with your preference if there is a strong one. |
That said from your part. I must add: the state of this branch is not final but it is a good result nonetheless. Please proceed. |
synchronized the PR via push to master |
Yes.
Done. Merged to develop in the fork. I also synchronized the You can push onto the Also, I tend to recommend this form of operation util a project gets a bit more stable. If a project has released already, has a stable stand, then open PRs for weeks on end are a different (and OK) story. I hope some of this makes sense. Thanks. |
Hurray! |
No description provided.